﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Security.Cryptography;

using System.Threading;
using System.Windows.Threading;
using System.Threading.Tasks;


namespace d8
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        private Task rollDiceTask = null;


        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            RollDice();
        }

        private void lblD8_MouseDown(object sender, MouseButtonEventArgs e)
        {
            RollDice();
        }

        //-------------------------------------------

        private void RollDice()
        {
            if (rollDiceTask == null || rollDiceTask.Status == TaskStatus.RanToCompletion)
            {
                bool isFirstRun = rollDiceTask == null;
                rollDiceTask = Task.Factory.StartNew(() => RollDiceTask(isFirstRun));
            }

            //// equally probable distribution check

            //int[] iar = new int[8];

            //for (int i = 0; i < 8000; i++)
            //{
            //    d8 = GetRandomWithinRange(7) + 1;
            //    iar[d8 - 1]++;
            //}

            //d8 = d8;    // set breakpoint here and check the iar[] array
        }

        //--------------------------------------------------

        private void RollDiceTask(bool isFirstRun)
        {
            int d8 = GetRandomWithinRange(7) + 1;    // generate random number [0..7] + 1

            if (!isFirstRun)
            {
                ExecuteAction(new Action(() => { lblD8.Content = ""; }));
                Thread.Sleep(200); // this line won't make UI freeze.
            }
            ExecuteAction(new Action(() => { lblD8.Content = d8.ToString(); }));
        }

        //--------------------------------------------------

        private void ExecuteAction(Action a)
        {
            if (this.Dispatcher.CheckAccess())
                a.Invoke();
            else
                this.Dispatcher.Invoke(a, DispatcherPriority.ApplicationIdle);
        }

        //--------------------------------------------------

        private int GetRandomWithinRange(int maxValue)
        {
            if (maxValue < 0)
                throw new ArgumentOutOfRangeException("maxValue must be greater than 0");
            if (maxValue == 0)
                return 0;   // the only one possible result :)

            return (int)GetRandomWithinRange((UInt32)maxValue);
        }

        //--------------------------------------------------

        /// <summary>
        /// Generates unbiased random number within range [0..maxValue]
        /// </summary>
        /// <param name="maxValue"></param>
        /// <returns></returns>
        private UInt32 GetRandomWithinRange(UInt32 maxValue)
        {
            if (maxValue == 0)
                return 0;   // the only one possible result :)

            byte[] arRnd = new byte[4];
            rng.GetBytes(arRnd);
            UInt32 uiRnd, uiRndLimited;

            // how much bits maxValue is using?
            UInt32 usedBits = 1;
            UInt32 maxval = maxValue;
            while (maxval >= 1)
            {
                usedBits++;
                maxval = maxval / 2;
            }

            UInt32 mask = (UInt32)Math.Pow(2, usedBits) - 1;

            while (true)    // yes, it's theoretically infinite cycle :)  we must have some luck to break through!
            {
                // generate a random value
                rng.GetBytes(arRnd);
                uiRnd = BitConverter.ToUInt32(arRnd, 0);
                // and limit this value
                uiRndLimited = uiRnd & mask;

                // check if it's not exceed maxValue...
                if (uiRndLimited <= maxValue)
                    return uiRndLimited;    // and done! :)
            }
        }
    }
}
